using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Pacman.Simulator;
using NeuralNetwork;
using System.Drawing;

namespace Pacman.Implementations
{
	public class SectorPac : BasePacman
	{
		private const int MAXSECTORDISTANCE = 6;
		private const bool DEBUG = false;
				
		public List<Sector> Sectors = null;
		
		StateInfo.PillPath avoidancePoint = null;
		LinkedList<DistanceNode> possibles;

		public SectorPac()
			: base("SectorPac","1") {	
		}

		/*public override void Draw(Graphics g, int[] dangerColors) {
			for( int i = 0; i < Sectors.Count; i++ ){
				double danger = dangerColors[i] * 56;
				int green = 255;
				int red = 0;
				int blue = 0;
				if( dangerColors[i] == -2 ) {
					continue;
				} else if( dangerColors[i] == -1 ) {
					green = 0;
					blue = 255;
				} else {
					if( danger < 255 ) {
						red += (int)danger;
					} else {
						red = 255;
						green -= ((int)danger - 255);
					}
				}
				g.FillRectangle(new SolidBrush(Color.FromArgb(100, red, green, blue)), new Rectangle(Sector.GameState.Map.Nodes[Sectors[i].X, Sectors[i].Y].CenterX, Sector.GameState.Map.Nodes[Sectors[i].X, Sectors[i].Y].CenterY, Sectors[i].Width * 9, Sectors[i].Height * 9));
			}
		}

		public override void Draw(Graphics g) {
			foreach( Sector sector in Sectors ) {
				double danger = sector.GetDanger() * 510;
				int green = 255;
				int red = 0;
				if( danger < 255 ) {
					red += (int)danger;
				} else {
					red = 255;
					green -= ((int)danger - 255);
				}
				g.FillRectangle(new SolidBrush(Color.FromArgb(100, red, green, 0)), new Rectangle(Sector.GameState.Map.Nodes[sector.X,sector.Y].CenterX,Sector.GameState.Map.Nodes[sector.X,sector.Y].CenterY,sector.Width*9,sector.Height*9));				
			}
		}*/

		public override void Draw(System.Drawing.Graphics g) {
			Prediction prediction = new Prediction(Sector.GameState, 9);
			for( int x = 0; x < Map.Width; x++ ) {
				for( int y = 0; y < Map.Height; y++ ) {
					Node node = Sector.GameState.Map.Nodes[x, y];
					if( node.Walkable ) {
						g.FillEllipse(new SolidBrush(Color.FromArgb(255, (int)(prediction.DangerMaps[9].Danger[x, y] * 255), 0, 0)), new Rectangle(new Point(node.CenterX, node.CenterY), new Size(8, 8)));
					}
				}
			}
		}

		private class DirectionNode
		{
			public readonly Node Node;
			public readonly Direction Direction;

			public DirectionNode(Node node, Direction direction) {
				this.Node = node;
				this.Direction = direction;
			}
		}

		public override void WriteData(StreamWriter sw, int sectorIndex) {			
			// Prediction based direction danger data
			Prediction prediction = new Prediction(Sector.GameState, 9);
			Direction possible = (Direction)sectorIndex;
			int nodeCount = 0;
			double dangerCount = 0.0;
			List<DirectionNode> nodes = new List<DirectionNode>();
			nodes.Add(new DirectionNode(Sector.GameState.Pacman.Node,possible));
			for( int i = 1; i < 10; i++ ){
				List<DirectionNode> newNodes = new List<DirectionNode>();
				foreach( DirectionNode dn in nodes ) {
					foreach( Node dnPossible in dn.Node.GhostPossibles[(int)dn.Direction] ) {
						dangerCount += prediction.DangerMaps[i - 1].Danger[dnPossible.X, dnPossible.Y];
						nodeCount++;
						newNodes.Add(new DirectionNode(dnPossible, dn.Node.GetDirection(dnPossible)));
					}
				}
				if( i % 3 == 0 ) {
					sw.Write(dangerCount / nodeCount + ";");
					nodeCount = 0;
					dangerCount = 0.0;
				}
				nodes = newNodes;
			}

			/* -- DIRECTION DANGER DATA
			foreach( Simulator.Ghosts.Ghost ghost in Sector.GameState.Ghosts ) {
				// angles to pacman				
				double angleToPacman = Math.Atan2(ghost.Node.Y - Sector.GameState.Pacman.Y, ghost.Node.X - Sector.GameState.Pacman.X);
				sw.Write( ( angleToPacman + Math.PI ) / (2 * Math.PI) + ";");
				// distance to pacman
				Node.PathInfo path = Sector.GameState.Pacman.Node.ShortestPath[ghost.Node.X, ghost.Node.Y];
				int distance = 40;
				if( path != null ) {
					distance = path.Distance;
				}
				sw.Write(distance + ";");
			}*/

			/* -- SECTOR DANGER DATA
			Sector sector = Sectors[sectorIndex];
			// distance to pacman
			sw.Write((int)Math.Abs(sector.TargetX - Sector.GameState.Pacman.Node.X) + (int)Math.Abs(sector.TargetY - Sector.GameState.Pacman.Node.Y) + ";");
			// distance to ghosts
			List<int> ghostDistanceToSector = new List<int>();
			foreach( Simulator.Ghosts.Ghost ghost in Sector.GameState.Ghosts ) {
				ghostDistanceToSector.Add((int)Math.Abs(sector.TargetX - ghost.Node.X) + (int)Math.Abs(sector.TargetY - ghost.Node.Y));
			}
			ghostDistanceToSector.Sort();
			foreach( int dist in ghostDistanceToSector ) {
				sw.Write(dist + ";");
			}
			// distance from ghosts to pacman
			List<int> ghostDistanceToPacman = new List<int>();
			foreach( Simulator.Ghosts.Ghost ghost in Sector.GameState.Ghosts ) {
				ghostDistanceToPacman.Add((int)Math.Abs(Sector.GameState.Pacman.Node.X - ghost.Node.X) + (int)Math.Abs(Sector.GameState.Pacman.Node.Y - ghost.Node.Y));
			}
			ghostDistanceToPacman.Sort();
			foreach( int dist in ghostDistanceToPacman ) {
				sw.Write(dist + ";");
			}*/
		}

		public override Direction Think(GameState gs) {
			Sector.GameState = gs;
			if( Sectors == null ) {
				Sectors = new List<Sector>();
				Sectors.Add(Sector.GetSector(0, 0, 13, 9));
				Sectors.Add(Sector.GetSector(14, 0, 13, 9));
				Sectors.Add(Sector.GetSector(0, 10, 8, 8));
				// sectors.Add(new sector(9,10,9,8)); // mid-sector not at good target
				Sectors.Add(Sector.GetSector(19, 10, 8, 8));
				Sectors.Add(Sector.GetSector(0, 19, 13, 9));
				Sectors.Add(Sector.GetSector(14, 19, 13, 9));	
			}
			Prediction prediction = new Prediction(gs, 15);
			//Console.WriteLine("");*/
			//Console.WriteLine(inputs[0] + "");
			//double networkResult = sectorChangeLearn.GetOutput(inputs)[0];
			//Console.WriteLine("NEURAL: " + networkResult + "," + inputs[0]);			

			// sectors ...
			foreach( Sector s in Sectors ) {
				s.Danger = 0f;
			}
			Sector pacSector = null;
			foreach( Prediction.DangerMap dm in prediction.DangerMaps ) {
				foreach( Sector s in Sectors ) {
					if (s.Contains(gs.Pacman.Node)) {
						pacSector = s;
					}
					if( true ) { // regular prediction
						for( int dx = s.X; dx < s.X + s.Width; dx++ ) {
							for( int dy = s.Y; dy < s.Y + s.Height; dy++ ) {
								s.Danger += dm.Danger[dx, dy];
							}
						}
					} else { // neural prediction
						s.Danger = s.GetDanger();
					}
				}
			}
			int safeId = 0;
			Sector safestSector = Sectors[0];
			for (int i=1; i < Sectors.Count; i++) {
				if( Sectors[i].GetNormalizedDanger() < safestSector.GetNormalizedDanger() ) {
					safestSector = Sectors[i];
					safeId = i;
				}
			}
			if (DEBUG) Console.Write("Safest Sector: " + safeId + ", ");
			
			Node.PathInfo sectorTarget = gs.Pacman.Node.ShortestPath[safestSector.TargetX,safestSector.TargetY];
			if( (pacSector != null && pacSector.Danger == 0) || 
				sectorTarget == null 
				|| (pacSector != null && pacSector.Danger / safestSector.Danger > 1.1) 
				|| sectorTarget.Distance < MAXSECTORDISTANCE ) {
				if( DEBUG ) Console.Write("Get nearest pill, ");
				StateInfo.PillPath pp = StateInfo.NearestPill(gs.Pacman.Node, gs);
				if (pp == null) {
					if( DEBUG ) Console.WriteLine("No paths found!");
					return Direction.None;
				}
				sectorTarget = pp.PathInfo;
			}
			
			// find new target (pill furthest away)			
			StateInfo.PillPath pillPath = StateInfo.FurthestPill(gs.Pacman.Node, gs);
			if( pillPath.PathInfo != null ) {
				avoidancePoint = pillPath;
			}

			List<DirectionDanger> directionDangers = new List<DirectionDanger>();
			if( avoidancePoint != null ) {
				// run dijkstra to find path							
				foreach( Direction d in gs.Pacman.PossibleDirections() ) {
					// find best direction
					Direction bestDirection = Direction.None;
					float shortestRoute = float.MaxValue;
					possibles = new LinkedList<DistanceNode>();
					possibles.AddFirst(new LinkedListNode<DistanceNode>(new DistanceNode(gs.Pacman.Node.GetNode(d), 0.0f, d, 1)));
					while( possibles.Count > 0 ) {
						// find next lowest node
						DistanceNode node = possibles.First.Value;
						// found node
						if( node.Node == avoidancePoint.Target ) {
							if( node.Distance < shortestRoute ) {
								shortestRoute = node.Distance;
								bestDirection = d;
							}
							break;
						}
						// pop node
						possibles.Remove(node);
						// add adjacents
						foreach( Node gn in node.Node.GhostPossibles[(int)node.Direction] ) {
							// find danger
							float danger = 0.0f;
							if( node.Time < prediction.Iterations ) {
								danger = prediction.DangerMaps[node.Time + 1].Danger[node.Node.X, node.Node.Y] +
											prediction.DangerMaps[node.Time + 1].Danger[gn.X, gn.Y];
								if( danger > 1.0f ) {
									danger = 1.0f;
								}
							}
							// create new node
							DistanceNode newNode = new DistanceNode(gn, node.Distance + danger, node.Node.GetDirection(gn), node.Time + 1);
							// past iterations
							if( newNode.Time > prediction.Iterations || gn.Type == Node.NodeType.PowerPill ) {
								if( newNode.Distance < shortestRoute ) {
									shortestRoute = newNode.Distance;
									bestDirection = d;
								}
								continue;
							}
							LinkedListNode<DistanceNode> curNode = possibles.First;
							while( curNode != null && curNode.Value.Distance < newNode.Distance ) {
								curNode = curNode.Next;
							}
							if( curNode == null ) {
								possibles.AddLast(newNode);
							} else {
								possibles.AddAfter(curNode, newNode);
							}
						}
					}
					directionDangers.Add(new DirectionDanger(d, shortestRoute));
				}
			}
			/*foreach( Simulator.Ghosts.Ghost ghost in gs.Ghosts ) {
				Node.PathInfo gp = gs.Pacman.Node.ShortestPath[ghost.Node.X, ghost.Node.Y];
				if( gp != null && gp.Distance < 5 && (ghost.Chasing || ghost.RemainingFlee < 200) ) {
					List<Node> route = gs.Map.GetRoute(gs.Pacman.Node,ghost.Node);
					bool foundPPill = false;
					for (int i = 0; i<route.Count && i < 3; i++) {
						if (route[i].Type == Node.NodeType.PowerPill) {
							if( DEBUG ) Console.Write("P-Pill found, ");
							foundPPill = true;
						}
					}
					if (!foundPPill && gs.Pacman.Node.Type != Node.NodeType.PowerPill) {
						foreach( DirectionDanger dd in directionDangers ) {
							if( gp.Direction == dd.Direction ) {
								dd.Danger = 1.0f;
								if( DEBUG ) Console.Write("Direction: " + dd.Direction + " pruned, ");
							}
						}
					}
				}
			}*/

			// try to get to safest sector.
			if( sectorTarget != null ) {
				foreach( DirectionDanger dd in directionDangers ) {
					if( dd.Direction == sectorTarget.Direction ) {
						//double sectorMoveResult = sectorMoveLearn.GetOutput(new double[]{dd.Danger})[0];
						//Console.WriteLine("SectorMove: " + dd.Danger + ", " + sectorMoveResult);
						if( dd.Danger < 0.1f ) {
							if( DEBUG ) Console.WriteLine("Move: " + dd.Direction + " - " + dd.Danger);
							return sectorTarget.Direction;
						}
					}
				}
			}
			// else scramble
			Direction globalBestDirection = Direction.None;
			float bestDanger = 1.0f;
			foreach( DirectionDanger dd in directionDangers ) {
				if( dd.Danger <= bestDanger ) {
					bestDanger = dd.Danger;
					globalBestDirection = dd.Direction;
				}
			}
			if( DEBUG ) Console.WriteLine("Scramble: " + globalBestDirection + " - " + bestDanger);
			
			return globalBestDirection;
		}

		public override void LevelCleared() {
			if( DEBUG ) Console.WriteLine("------------ LEVEL CLEARED -------------");			
		}

		public override void EatenByGhost() {
			if( DEBUG ) Console.WriteLine("------------ PACMAN DIED ---------------");			
		}

		public override void EatPill() {
		}

		private class DirectionDanger
		{
			public readonly Direction Direction;
			public float Danger;

			public DirectionDanger(Direction direction, float danger) {
				this.Direction = direction;
				this.Danger = danger;
			}
		}

		private class DistanceNode
		{
			public readonly Node Node;
			public readonly float Distance;
			public readonly Direction Direction;
			public readonly int Time;

			public DistanceNode(Node node, float distance, Direction direction, int time) {
				this.Node = node;
				this.Distance = distance;
				this.Direction = direction;
				this.Time = time;
			}
		}
	}
}
